﻿using System;

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Audio;
using OpenTK.Audio.OpenAL;
using OpenTK.Input;


namespace Kepler
{

    class Game : GameWindow
    {

        Star sun;
        Vector3 sunPos = new Vector3(0.0f, 0.0f, 4.0f);
        Vector3 sunColor = new Vector3(1.0f, 1.0f, 0.0f); //these vectors are now unneeded for lighting, but are still required by the constructor
        Vector4 sunSpecular = new Vector4(1.0f, 1.0f, 0.0f, 1.0f); //not sure what the last element of these vector4s does
        Vector4 sunDiffuse = new Vector4(1.0f, 1.0f, 0.0f, 1.0f);  //( r, g, b, ?)
        Vector4 sunEmission = new Vector4(1.0f, 1.0f, 0.0f, 1.0f);
        float[] sunShininess = { 50.0f };

        Planet earth;
        Vector3 earthPos = new Vector3(0.5f, 0.0f, 4.0f);
        Vector3 earthColor = new Vector3(0.0f, 0.0f, 1.0f);
        Vector4 earthSpecular = new Vector4(0.0f, 0.0f, 1.0f, 1.0f);
        Vector4 earthDiffuse = new Vector4(0.0f, 0.0f, 1.0f, 1.0f);
        float[] earthShininess = { 50.0f };

        Moon moon;
        Vector3 moonPos = new Vector3(0.65f, 0.0f, 4.0f);
        Vector3 moonColor = new Vector3(0.5f, 0.5f, 0.5f);
        Vector4 moonSpecular = new Vector4(0.5f, 0.5f, 0.5f, 1.0f);
        Vector4 moonDiffuse = new Vector4(0.5f, 0.5f, 0.5f, 1.0f);
        float[] moonShininess = { 50.0f };

        Planet mars;
        Vector3 marsPos = new Vector3(1.0f, 0.0f, 4.0f);
        Vector3 marsColor = new Vector3(0.75f, 0.2f, 0.0f);
        Vector4 marsSpecular = new Vector4(0.75f, 0.2f, 0.0f, 1.0f);
        Vector4 marsDiffuse = new Vector4(0.75f, 0.2f, 0.0f, 1.0f);
        float[] marsShininess = { 50.0f };

        Moon phobos;
        Vector3 phobosPos = new Vector3(1.10f, 0.0f, 4.0f);
        Vector3 phobosColor = new Vector3(0.7f, 0.7f, 0.7f);
        Vector4 phobosSpecular = new Vector4(0.7f, 0.7f, 0.7f, 1.0f);
        Vector4 phobosDiffuse = new Vector4(0.7f, 0.7f, 0.7f, 1.0f);
        float[] phobosShininess = { 50.0f };

        Moon demios;
        Vector3 demiosPos = new Vector3(0.925f, 0.0f, 4.0f);
        Vector3 demiosColor = new Vector3(0.6f, 0.6f, 0.6f);
        Vector4 demiosSpecular = new Vector4(0.6f, 0.6f, 0.6f, 1.0f);
        Vector4 demiosDiffuse = new Vector4(0.6f, 0.6f, 0.6f, 1.0f);
        float[] demiosShininess = { 50.0f };

        /*
        Vector3 mercuryColor = new Vector3(0.5f, 0.5f, 0.5f);
        Vector3 venusColor = new Vector3(0.75f, 0.75f, 0.75f);
        Vector3 jupiterColor = new Vector3(0.75f, 0.4f, 0.0f);
        Vector3 saturnColor = new Vector3(0.8f, 0.8f, 0.6f);
        Vector3 uranusColor = new Vector3(0.3f, 0.6f, 0.7f);
        Vector3 neptuneColor = new Vector3(0.3f, 0.6f, 0.9f);
        */

        Vector4 noEmission = new Vector4(0.0f, 0.0f, 0.0f, 1.0f);

        Vector4 lightPosition = new Vector4(0.0f, 0.0f, 4.0f, 1.0f); 
        Vector2 mousePosition;
        CCamera theCamera;
                
        /// <summary>Creates a 1024x768 window with the specified title.</summary>
        public Game()
            : base(1024, 768, GraphicsMode.Default, "Kepler")
        {
            VSync = VSyncMode.On;
        }

        /// <summary>Load resources here.</summary>
        /// <param name="e">Not used.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            
            sun = new Star("Sun", sunPos, 0.2f, sunColor);
            earth = new Planet("Earth", earthPos, 0.05f, 1.0f, 0.2f , 0.5f, earthColor);
            mars = new Planet("Mars", marsPos, 0.03f, 1.0f, 0.5f , 1.0f, marsColor);
            moon = new Moon("Moon", moonPos, 0.02f, 1.0f, 2.0f, 0.15f, moonColor);
            phobos = new Moon("Phobos", phobosPos, 0.01f, 1.0f, 3.0f, 0.10f, phobosColor);
            demios = new Moon("Demios", demiosPos, 0.01f, 2.0f, 2.5f, 0.075f, phobosColor);

            sun.add(earth);
            sun.add(mars);
            earth.add(moon);
            mars.add(phobos);
            mars.add(demios);

            theCamera = new CCamera();

            //////////////////////////
            ///Begin Lighting Jippers
            //////////////////////////

            //float[] light_position = { 0.0f, 0.0f, 4.0f, 0.0f }; //coordinates of the center of the sun
            // modify to set the light color
            //float[] light_diffuse = { 0.0f, 0.0f, 4.0f, 0.0f };
            //float[] light_ambiant = { 0.0f, 0.0f, 4.0f, 0.0f };

            GL.ClearColor(0.0f, 0.0f, 0.0f, 0.0f); //black background

            GL.ShadeModel(ShadingModel.Smooth);

            GL.Light(LightName.Light0, LightParameter.Position, lightPosition);
            // uncomment to set the light color
            // GL.Light(LightName.Light0, LightParameter.Diffuse, light_diffuse);
            // GL.Light(LightName.Light0, LightParameter.Ambient, light_ambiant);
        
            GL.Enable(EnableCap.Lighting);
            GL.Enable(EnableCap.Light0);
            GL.Enable(EnableCap.DepthTest);
            //////////////////////////
            ///End Lighting Jippers
            //////////////////////////
            
        }

        /// <summary>
        /// Called when your window is resized. Set your viewport here. It is also
        /// a good place to set up your projection matrix (which probably changes
        /// along when the aspect ratio of your window).
        /// </summary>
        /// <param name="e">Not used.</param>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);

            GL.Viewport(ClientRectangle.X, ClientRectangle.Y, ClientRectangle.Width, ClientRectangle.Height);

            Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, Width / (float)Height, 1.0f, 64.0f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref projection);
        }

        /// <summary>
        /// Called when it is time to setup the next frame. Add you game logic here.
        /// </summary>
        /// <param name="e">Contains timing information for framerate independent logic.</param>
        protected override void OnUpdateFrame(FrameEventArgs e)
        {

            base.OnUpdateFrame(e);

            sun.tick();
 
            if (Keyboard[Key.Escape])
                Exit();

            if (Mouse[MouseButton.Left])
            {
               mousePosition = new Vector2((float)Mouse.X, (float)Mouse.Y);
               // mousePosition = new Vector2(Mouse.X, Mouse.Y);
               // ArcBall.Click(mousePosition);
            }

            if (Mouse[MouseButton.Right])
                mousePosition = new Vector2((float)Mouse.X, (float)Mouse.Y);

            if (Mouse[MouseButton.Middle])
                mousePosition = new Vector2((float)Mouse.X, (float)Mouse.Y);

            theCamera.Update(e.Time, this);

        }

        /// <summary>
        /// Called when it is time to render the next frame. Add your rendering code here.
        /// </summary>
        /// <param name="e">Contains timing information.</param>
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);

            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            //Matrix4 modelview = Matrix4.LookAt(Vector3.Zero, Vector3.UnitZ, Vector3.UnitY);
            GL.MatrixMode(MatrixMode.Modelview);
            //GL.LoadMatrix(ref modelview);

            GL.LoadMatrix(ref theCamera.matRotation);

            //GL.Color3(sunColor);
            //GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, sunDiffuse);
            //GL.Material(MaterialFace.Front, MaterialParameter.Specular, sunSpecular);
            //GL.Material(MaterialFace.Front, MaterialParameter.Shininess, sunShininess);
            GL.Material(MaterialFace.Front, MaterialParameter.Emission, sunEmission);
            DrawSphere(sun.position, sun.radius, 64);
            GL.Material(MaterialFace.Front, MaterialParameter.Emission, noEmission);

            //GL.Color3(earthColor);
            GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, earthDiffuse);
            GL.Material(MaterialFace.Front, MaterialParameter.Specular, earthSpecular);
            GL.Material(MaterialFace.Front, MaterialParameter.Shininess, earthShininess);
            DrawSphere(earth.position, earth.radius, 32);

            //GL.Color3(moonColor);
            GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, moonDiffuse);
            GL.Material(MaterialFace.Front, MaterialParameter.Specular, moonSpecular);
            GL.Material(MaterialFace.Front, MaterialParameter.Shininess, moonShininess);
            DrawSphere(moon.position, moon.radius, 32);

            //GL.Color3(marsColor);
            GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, marsDiffuse);
            GL.Material(MaterialFace.Front, MaterialParameter.Specular, marsSpecular);
            GL.Material(MaterialFace.Front, MaterialParameter.Shininess, marsShininess);
            DrawSphere(mars.position, mars.radius, 32);

            //GL.Color3(phobosColor);
            GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, phobosDiffuse);
            GL.Material(MaterialFace.Front, MaterialParameter.Specular, phobosSpecular);
            GL.Material(MaterialFace.Front, MaterialParameter.Shininess, phobosShininess);
            DrawSphere(phobos.position, phobos.radius, 32);

            //GL.Color3(demiosColor);
            GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, demiosDiffuse);
            GL.Material(MaterialFace.Front, MaterialParameter.Specular, demiosSpecular);
            GL.Material(MaterialFace.Front, MaterialParameter.Shininess, demiosShininess);
            DrawSphere(demios.position, demios.radius, 32);

            GL.End();

            SwapBuffers();
        }
        
        public void DrawSphere(Vector3 Center, float Radius, uint Precision)
        {
            if (Radius < 0f)
                Radius = -Radius;
            if (Radius == 0f)
                throw new DivideByZeroException("DrawSphere: Radius cannot be 0f.");
            if (Precision == 0)
                throw new DivideByZeroException("DrawSphere: Precision of 8 or greater is required.");

            const float HalfPI = (float)(Math.PI * 0.5);
            float OneThroughPrecision = 1.0f / Precision;
            float TwoPIThroughPrecision = (float)(Math.PI * 2.0 * OneThroughPrecision);

            float theta1, theta2, theta3;
            Vector3 Normal, Position;

            for (uint j = 0; j < Precision / 2; j++)
            {
                theta1 = (j * TwoPIThroughPrecision) - HalfPI;
                theta2 = ((j + 1) * TwoPIThroughPrecision) - HalfPI;

                GL.Begin(BeginMode.TriangleStrip);
                for (uint i = 0; i <= Precision; i++)
                {
                    theta3 = i * TwoPIThroughPrecision;

                    Normal.X = (float)(Math.Cos(theta2) * Math.Cos(theta3));
                    Normal.Y = (float)Math.Sin(theta2);
                    Normal.Z = (float)(Math.Cos(theta2) * Math.Sin(theta3));
                    Position.X = Center.X + Radius * Normal.X;
                    Position.Y = Center.Y + Radius * Normal.Y;
                    Position.Z = Center.Z + Radius * Normal.Z;

                    GL.Normal3(Normal);
                    GL.TexCoord2(i * OneThroughPrecision, 2.0f * (j + 1) * OneThroughPrecision);
                    GL.Vertex3(Position);

                    Normal.X = (float)(Math.Cos(theta1) * Math.Cos(theta3));
                    Normal.Y = (float)Math.Sin(theta1);
                    Normal.Z = (float)(Math.Cos(theta1) * Math.Sin(theta3));
                    Position.X = Center.X + Radius * Normal.X;
                    Position.Y = Center.Y + Radius * Normal.Y;
                    Position.Z = Center.Z + Radius * Normal.Z;

                    GL.Normal3(Normal);
                    GL.TexCoord2(i * OneThroughPrecision, 2.0f * j * OneThroughPrecision);
                    GL.Vertex3(Position);
                }
                GL.End();
            }
        }
    
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            // The 'using' idiom guarantees proper resource cleanup.
            // We request 30 UpdateFrame events per second, and unlimited
            // RenderFrame events (as fast as the computer can handle).
            using (Game game = new Game())
            {
              game.Run(30.0);
              // game.Run(120.0);
            }
        }
    }

}
